<div id="app">
    <div class="header">
        <select id="contentType" v-model="selectedMethod">
            <option v-for="method in methods" :key="method" :value="method">{{ method }}</option>
          </select>
        <input id="url" type="text" placeholder="请输入接口地址" v-model="url" @input="parseUrl()">
        <input id="send" type="button" value="发送" @click="send">
    </div>
    <div class="neck">
        <ul>
            <li><a href="#" v-bind:class="{active:activeWho==='Header'}" @click="activeThis('Header')">Header</a></li>
            <li><a href="#" v-bind:class="{active:activeWho==='Query'}" @click="activeThis('Query')">Query</a></li>
            <li><a href="#" v-bind:class="{active:activeWho==='Body'}" @click="activeThis('Body')">Body</a></li>
        </ul>
        <div class="panel">
            <div class="scroll">
                <!-- header 对应的panel -->
                <table v-show="activeWho=='Header'">
                    <tr>
                        <th class="pname">参数名</th>
                        <th class="pvalue">参数值</th>
                    </tr>
                    <tr v-for="headerParam,index in headerParams" :key="headerParam.id" :value="headerParam">
                        <td>
                            <input type="text" class="thePName" v-model="headerParam.name" @blur="addRow(headerParam.id,headerParam.name,headerParam.value,'headerParams')">
                            <select name="type" tabindex="-1" class="thePType" v-model="headerParam.type">
                                <option v-for="ptype in paramTypes" :key="ptype" :value="ptype">{{ptype}}</option>
                            </select>
                        </td>
                        <td>
                            <input type="text" class="thePValue" name="value" v-model="headerParam.value" @blur="addRow(headerParam.id,headerParam.name,headerParam.value,'headerParams')">
                            <input type="button" tabindex="-1" class="theDelete" value="删除" @click="removeRow(headerParam.id,'headerParams')">
                        </td>
                    </tr>
                </table>
            </div>
            
            <!-- Query 对应的panel -->
            <div class="scroll">
                <table v-show="activeWho=='Query'">
                    <tr>
                        <th class="pname">参数名</th>
                        <th class="pvalue">参数值</th>
                    </tr>
                    <tr v-for="queryParam,index in queryParams" :key="queryParam.id" :value="queryParam">
                        <td>
                            <input type="text" class="thePName" v-model="queryParam.name" @blur="addRow(queryParam.id,queryParam.name,queryParam.value,'queryParams')"  @input="resetUrlAndQueryParams()">
                            <select name="type" tabindex="-1" class="thePType" v-model="queryParam.type">
                                <option v-for="ptype in paramTypes" :key="ptype" :value="ptype">{{ptype}}</option>
                            </select>
                        </td>
                        <td>
                            <input type="text" class="thePValue" name="value" v-model="queryParam.value" @blur="addRow(queryParam.id,queryParam.name,queryParam.value,'queryParams')" @input="resetUrlAndQueryParams()">
                            <input type="button" tabindex="-1" class="theDelete" value="删除" @click="removeRow(queryParam.id,'queryParams')">
                        </td>
                    </tr>
                </table>
            </div>
            
            <!-- Body 对应的panel -->
            <div class="bodyPanel" v-show="activeWho=='Body'">
                <label v-for="(item,index) in contentTypes" :key="index">
                    <input type="radio" name="contentType" :value="item.name" v-model="contentType.name" @click="selectContentType(item.name,item.value)">
                    {{item.name}}
                </label>
                <!-- body-form-data -->
                <div class="scroll">
                    <table v-show="contentType.name=='form-data'">
                        <tr>
                            <th class="pname">参数名</th>
                            <th class="pvalue">参数值</th>
                        </tr>
                        <tr v-for="formDataParam,index in formDataParams" :key="formDataParam.id" :value="formDataParam">
                            <td>
                                <input type="text" class="thePName" v-model="formDataParam.name" @blur="addRow(formDataParam.id,formDataParam.name,formDataParam.value,'formDataParams')">
                                <select name="type" tabindex="-1" class="thePType" v-model="formDataParam.type">
                                    <option v-for="ptype in formDataParamTypes" :key="ptype" :value="ptype">{{ptype}}</option>
                                </select>
                            </td>
                            <td>
                                <div style="align-items: center;height: 100%;">
                                    <input v-bind:type="formDataParam.type=='File'?'file':'text'" class="thePValue" name="value" v-model="formDataParam.value" @blur="addRow(formDataParam.id,formDataParam.name,formDataParam.value,'formDataParams')">
                                    <input type="button" tabindex="-1" class="theDelete" value="删除" @click="removeRow(formDataParam.id,'formDataParams')">
                                </div>
                            </td>
                        </tr>
                    </table>
                </div>
                <!-- body-urlencoded -->
                <div class="scroll">
                    <table v-show="contentType.name=='urlencoded'">
                        <tr>
                            <th class="pname">参数名</th>
                            <th class="pvalue">参数值</th>
                        </tr>
                        <tr v-for="urlEncodedParam,index in urlEncodedParams" :key="urlEncodedParam.id" :value="urlEncodedParam">
                            <td>
                                <input type="text" class="thePName" v-model="urlEncodedParam.name" @blur="addRow(urlEncodedParam.id,urlEncodedParam.name,urlEncodedParam.value,'urlEncodedParams')">
                                <select name="type" tabindex="-1" class="thePType" v-model="urlEncodedParam.type">
                                    <option v-for="ptype in paramTypes" :key="ptype" :value="ptype">{{ptype}}</option>
                                </select>
                            </td>
                            <td>
                                <input type="text" class="thePValue" name="value" v-model="urlEncodedParam.value" @blur="addRow(urlEncodedParam.id,urlEncodedParam.name,urlEncodedParam.value,'urlEncodedParams')">
                                <input type="button" tabindex="-1" class="theDelete" value="删除" @click="removeRow(urlEncodedParam.id,'urlEncodedParams')">
                            </td>
                        </tr>
                    </table>
                </div>
                <div class="scroll">

                </div>
                <!-- body-json -->
                <div v-show="contentType.name=='json'" class="theJson">
                    <span id="tips" v-bind:class="{right:(tipLevel=='right'),error:(tipLevel=='error'),normal:(tipLevel=='normal')}">{{tip}}</span>
                    <div>
                        <textarea v-model="inputJson" @blur="formatJson"></textarea>
                    </div>
                  </div>
            </div>
        </div>
    </div>
    <div class="response json-viewer scroll">
        <pre>{{ formattedJson }}</pre>
    </div>
</div>
<!--引入vue.js-->
<script src="jquery.min.js"></script>
<script src="vue.js"></script>
<script src="axios.min.js"></script>
<script src="jquery.json-viewer.js"></script>
<link rel="stylesheet" href="jquery.json-viewer.css">
<script>
    const app = new Vue({
        el:"#app",
        data:{
            url:'http://localhost:8080/station/login',
            activeWho: 'Header',
            selectedMethod: 'POST', // 用于存储选中的方法
            methods: ['POST', 'GET'], // 可以替换为你需要的选项
            paramTypes: ['String','Float','Integer','Boolean'],
            formDataParamTypes: ['String','Float','Integer','Boolean','File'],
            contentType: {name:'json',value:'application/json'},
            contentTypes: [
                {id:1,name:'none',value:''},
                {id:2,name:'form-data',value:'multipart/form-data'},
                {id:3,name:'urlencoded',value:'application/x-www-form-urlencoded'},
                {id:4,name:'json',value:'application/json'}
            ],
            headerParams: [
                {id:1,name:'',value:'',type:'String'}
            ],
            queryParams: [
                {id:1,name:'',value:'',type:'String'}
            ],
            formDataParams: [
                {id:1,name:'',value:'',type:'String'}
            ],
            urlEncodedParams: [
                {id:1,name:'',value:'',type:'String'}
            ],
            inputJson: '',
            tip: '请在下方区域输入json数据',
            tipLevel: 'normal',
            formattedJson: null,
            convertedString: ''
        },
        methods:{
            selectContentType(name,value){
                this.contentType.name = name;
                this.contentType.value = value;
            },
            addRow(theId,name,value,flag){
                // 如果没填完整也不会生成下一行
                if (name === '') {
                    return;
                }
                // 判断headerParams是否已经存在该id的对象 
                // 如果存在直接return 
                let newId = theId + 1;
                if (this[flag].find(({id,name,value}) => id === newId)) {
                    // this.resetUrlAndQueryParams();
                    return;
                }
                // 如果不存在 添加一个id+1的对象
                this[flag].push({id: newId, name: '', type: 'String', value: ''});
                // this.resetUrlAndQueryParams();
            },
            removeRow(theId,flag){
                let indexToRemove = -1;
                let maxIndex = 0;
                for(let i = 0;i<this[flag].length;i++){
                    let o = this[flag][i];
                    if(theId===o.id){
                        indexToRemove = i;
                    }
                    maxIndex = maxIndex>i?maxIndex:i;
                }
                if (indexToRemove > -1&&maxIndex != indexToRemove) {
                    this[flag].splice(indexToRemove, 1);
                }
                this.resetUrlAndQueryParams();
            },
            activeThis(str){
                this.activeWho = str;
            },
            formatJson() {
                try {
                    if(this.inputJson==''){
                        this.tip='请在下方区域输入json数据';
                        this.tipLevel='normal';
                        return;
                    }
                    this.inputJson = JSON.stringify(JSON.parse(this.inputJson), null, 2);
                    this.tip='验证通过,点击发送即可发送请求';
                    this.tipLevel='right';
                } catch (error) {
                    this.tip = this.inputJson+'\n输入的不是有效的 JSON 格式\n'+error;
                    this.tipLevel='error';
                }
            },
            resetUrlAndQueryParams(){
                let originUrl = this.url.split("?")[0];
                for(let i = 0;i<this.queryParams.length;i++){
                    let o = this.queryParams[i];
                    if(o.name===''){
                        continue;
                    }
                    if(i===0){
                        originUrl+='?'+o.name+'='+o.value;
                    }else{
                        originUrl+='&'+o.name+'='+o.value;
                    }
                }
                this.url=originUrl;
            },
            parseUrl(){
                let theQueryParmas=[];
                let theId = 1;
                if('Query'===this.activeWho){//只在Query时处理
                    let urlArr = this.url.split("?");
                    if(urlArr.length>1){
                        let paramsStr = urlArr[1];
                        if(paramsStr===''){
                            this.queryParams = [{id:1,name:'',value:'',type:'String'}];
                            return;
                        }
                        let paramsArr = paramsStr.split('&');
                        for(let i = 0;i<paramsArr.length;i++){
                            let nameAndValueStr = paramsArr[i];
                            if(!nameAndValueStr.includes('=')){//不包含=说明刚开始或者全是&
                                theQueryParmas.push({id:theId,name:nameAndValueStr,value:'',type:'String'});
                                theId++;
                            }else{//包含= 说明可拆分name和value
                                let nameAndValueArr = nameAndValueStr.split('=');//a===b
                                let theValue = nameAndValueArr[1];//取=后面的当作value
                                if(nameAndValueArr.length>2){//解决value是多个=的问题
                                    theValue = nameAndValueStr.slice(nameAndValueStr.indexOf('='));
                                }
                                theQueryParmas.push({id:theId,name:nameAndValueArr[0],value:theValue,type:'String'})
                                theId++;
                            }
                        }
                    }
                }
                if(theQueryParmas.length<1){
                    this.queryParams=[{id:1,name:'',value:'',type:'String'}];
                }else{
                    //取最后一个元素 调用addRow方法
                    this.queryParams = theQueryParmas;
                    let nowId = theQueryParmas[theQueryParmas.length-1].id;
                    let nowName = theQueryParmas[theQueryParmas.length-1].name;
                    let nowValue = theQueryParmas[theQueryParmas.length-1].value;
                    this.addRow(nowId,nowName,nowValue,"queryParams");
                }
            },
            send(){
                if('error'==this.tipLevel){
                    alert("输入数据格式错误(推测为JSON格式输入错误)");
                    return;
                }

                //需要的data有如下

                //selectedMethod 选中的方法 GET/POST

                //contentType: none form-data urlencoder json
                //headerParams
                let theHeaders = {};
                if(this.contentType.name!='none'){
                    theHeaders = {'Content-Type':this.contentType.value}
                }
                //queryParams
                for(let i = 0;i<this.headerParams.length;i++){
                    let theHeaderParam = this.headerParams[i];
                    if(theHeaderParam.name===''){
                        continue;
                    }
                    this.convertType(theHeaderParam.type,theHeaderParam.value);
                    let headerParamValue = this.convertedString;
                    theHeaders[theHeaderParam.name]=headerParamValue;
                }
                let theData = {};
                //formDataParams
                if(this.contentType.name=='form-data'){
                    for(let i = 0;i<this.formDataParams.length;i++){
                        let theFormDataParam = this.formDataParams[i];
                        if(theFormDataParam.name===''){
                            continue;
                        }
                        this.convertType(theFormDataParam.type,theFormDataParam.value);
                        let formDataValue = this.convertedString;
                        theData[theFormDataParam.name]=formDataValue;
                    }
                }
                
                //urlEncodedParams
                if(this.contentType.name==='urlencoded'){
                    let params = new URLSearchParams();//创建一个新的URLSearchParams实例来存储你的参数

                    for(let i = 0;i<this.urlEncodedParams.length;i++){
                        let theUrlEncodedParam = this.urlEncodedParams[i];
                        if(theUrlEncodedParam.name===''){
                            continue;
                        }
                        this.convertType(theUrlEncodedParam.type,theUrlEncodedParam.value);
                        let urlEncodedParamValue = this.convertedString;
                        params.append(theUrlEncodedParam.name,urlEncodedParamValue);
                    }
                    theData = params;
                }

                //inputJson
                if(this.contentType.name==='json'){
                    theData = this.inputJson;
                }

                axios({
                    url: this.url,
                    method: this.selectedMethod,
                    responseType: 'text',
                    headers: theHeaders,
                    data: theData
                })
                .then(response => {
                    // let h = response.headers;
                    // if(h.hasOwnProperty(this.tokenKey)){
                    //     this.tokenValue = h[this.tokenKey];
                    // }
                    let afterJson = JSON.parse(response.data);
                    this.formattedJson = afterJson;
                })
                .catch(error=>{
                    console.log(error);
                    this.formattedJson = error;
                })
            },
            convertType(type,value){
                switch (type) {
                        case 'String':
                            this.convertedString = String(value);
                            break;
                        case 'Integer':
                            this.convertedString = Number(value);
                            break;
                        case 'Float':
                            this.convertedString = parseFloat(value);
                            break;
                        case 'Boolean':
                            this.convertedString = 'true'===value;  
                            break;    
                        default:
                            break;
                }
                // alert(this.convertedString+typeof(this.convertedString));

            }
        }
    })
</script>
<style>
html {
    font-family: sans-serif;
}
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
#app{
    width: 80%;
    margin: 0 auto;
}
/* header */
.header{
    margin-top: 30px;
    height: 50px;
    padding: 5px;
    border: none;
    background-color: #F2F2F2;

}
#contentType{
    width: 100px;
    height: 40px;
    border-radius: 5px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
    border-color: aliceblue;
    background-color: white;
    color: #FFB400;
    font-weight: bolder;
    outline: none;
}
#url{
    text-indent: 1em;
    height: 40px;
    font-weight: bold;
    width: calc(100% - 210px);;
    border: none;
    background-color: #F2F2F2;
    outline: none;
}
#url:focus{
    border: 1px solid #43E677; 
    border-radius: 5px;
}
#send{
    width: 100px;
    height: 40px;
    border-radius: 5px;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
    background-color: #61CBA0;
    border: none;
    color: rgb(255,255,255);
    font-weight: bold;
}
#send:hover{
    background-color: #8AD8B9;
}
/* neck */
.neck{
    margin: 0 auto;
    /* overflow: auto; */
}
.neck li{
    float: left;
    list-style-type: none;
    width: 150px;
    height: 40px;
    font-weight:500;
}
.neck li a{
    display: inline-block;
    text-decoration: none;
    width: 100%;
    line-height: 40px;
}
.neck .panel{
    height: 300px;
    /* background-color: #FFB400; */
    clear: both;
}
.active{
    color: #43E677;
}
/* panel */
table {
    display: table;
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0px;
    max-width: none;
    min-width: 100px;
}
table,
th,
td {
  height: 40px;
  border: 1px solid black;
  padding: 1px;
}
table input,select{
    height: 100%;
    font-size: 16px;
}
table .pname{
    width: 30%;
}
table .pvalue{
    width: 70%;
}
.thePName{
    width: 70%;
    text-indent: 1em;
    font-weight: 500;
    border: none;
    outline: none;
}
.thePType{
    width: 28%;
    border: none;
    font-weight: 500;
    color: #43E677;
    outline: none;
}
.thePValue{
    width: 94%;
    text-indent: 1em;
    font-weight: 500;
    border: none;
    outline: none;
}
.theDelete{
    width: 5%;
    font-weight: 500;
    font-size: 14px;
    color: white;
    background-color: #D70021;
    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
    border-radius: 5px;
    border: none;
}
.theDelete:hover{
    background-color: #a81128;
}

.headerPName:hover,.headerPType:hover,.headerPValue:hover{
    border: 1px solid #43E677; 
    border-radius: 5px;
}

/* body */
.bodyPanel{
    height: 300px;
}
.bodyPanel label{
    height: 40px;
    width: 100%;
}
.bodyPanel label:not(:first-child){
    margin-left: 20px;
}
.bodyPanel label input{
    accent-color: rgb(78, 151, 48);
}
.bodyPanel table{
    margin-top: 10px;
}
::file-selector-button {
    font-size: 16px;
    background-color: #43E677;
    color: #fff;
    border: none;
    /* border: 1px solid ; */
    border-radius: 5px;
    box-sizing: border-box;
}
.theJson{
    width: 100%;
}
.theJson textarea{
    width: 100%;
    height: 300px;
}
.json-viewer pre {
    white-space: pre-wrap;
    word-wrap: break-word;
}
textarea {
  /* 设置字体和字号 */
  font-family: monospace;
  font-size: 16px;

  /* 设置文本颜色 */
  color: #333;

  /* 设置边框 */
  border: 1px solid #43E677;

  /* 设置背景颜色 */
  background-color: #fff;

  outline: none;
}
#tips{
    display: block;
    width: 100%;
    line-height: 40px;
    height: 40px;
    /* color: #FFB400; */
    font-weight: 500;
}
.normal{
    color:#FFB400;
}
.right{
    color: #43E677;
}
.error{
    color: #D70021;
}

.response{
    margin-top: 100px;
    width: 100%;
    height: 300px;
    border: 1px solid #888;
    /* background-color: #FFB400; */
}

.scroll {
  overflow-y: scroll;
  max-height: 100%;
  /* margin-bottom: 40px; */
}
/* 设置滚动条宽度为 10px */
::-webkit-scrollbar {
  width: 8px;
}

/* 设置滚动条背景颜色为灰色 */
/* ::-webkit-scrollbar-track {
  background-color: #f1f1f1;
} */

/* 设置滚动条滑块的颜色为深灰色 */
::-webkit-scrollbar-thumb {
  background-color: #888;
  border-radius: 4px;
}

/* 当滚动条滑块被鼠标悬停时，改变颜色为浅灰色 */
::-webkit-scrollbar-thumb:hover {
  background-color: #555;
}


</style>